package cn.haocxy.jfm.filelist;

import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Collection;

import javafx.application.Platform;
import cn.haocxy.jfm.file.DirWatcher;
import cn.haocxy.jfm.file.FileFilter;
import cn.haocxy.jfm.file.FilePath;
import cn.haocxy.jfm.util.EventHandlers;
import cn.haocxy.jfm.util.Some;
import cn.haocxy.jfm.util.Utils;

/**
 * 文件列表模型。
 * <p>
 * 保存文件列表（文件夹中的内容），对界面提供展示的内容， 如选择/不选的状态变化。 由监听器根据状态的变化来决定执行什么操作。
 * <p>
 * 使用视图-模型的意义在于用户可以随时改变界面元素，如切换显示方式，而不影响已经进行的操作，
 * 比如可以切换以图标方式显示或者以列表方式显示，而不会丢失选择/不选的状态。
 * <p>
 * 因为模型频繁与GUI交互，所以对模型的操作也应该在GUI线程内。
 * 
 * @author 蔡心宇
 *
 */
public final class FileList {

	private FilePath currentDir = null;

	// 文件夹内容
	private final Some<FileItem> items = new Some<>();

	// 已选项
	private final Some<FileItem> selectedItems = new Some<>();

	// 清空文件项时的监听器
	private final EventHandlers<ClearEvent> clearEventHandlers = new EventHandlers<>();

	// 添加文件项时的监听器
	private final EventHandlers<AddEvent> addEventHandlers = new EventHandlers<>();

	// 切换路径时的监听器
	private final EventHandlers<DirEvent> dirEventHandlers = new EventHandlers<>();

	// 文件过滤器, 只有通过过滤器的文件项才会被显示
	private final Some<FileFilter> filters = new Some<>();

	// 监视文件夹的变化
	private DirWatcher watcher = null;

	public FileList(String path) {
		setDir(path);
	}

	public FileList(FilePath path) {
		setDir(path);
	}

	public Collection<FileItem> getFileItems() {
		return items.elems();
	}

	public boolean hasParent() {
		return currentDir.hasParent();
	}

	public void setDir(String path) {
		setDir(FilePath.fromString(path));
	}

	public void setDir(FilePath path) {
		if (path.isDirectory() && path.canOpen()) {
			FilePath old = currentDir;
			currentDir = path;
			if (currentDir == null) {
				throw new RuntimeException("path cannot be null");
			}
			if (old == null || !old.equals(currentDir)) {
				flush();
			}
			onDir(old);
			setDirWatcher();
		}
	}

	/*
	 * (重新或首次)载入当前路径的文件项模型，即刷新
	 */
	public void flush() {
		items.clear();
		selectedItems.clear();
		currentDir.getChildren().forEach(e -> {
			if (filt(e)) {
				FileItem item = new FileItem(this);
				item.setPath(e);
				items.add(item);
			}
		});
		clearEventHandlers.applyAll(new ClearEvent());
		addEventHandlers.applyAll(new AddEvent(items.elems()));
	}

	public EventHandlers<ClearEvent> getClearEventHandlers() {
		return clearEventHandlers;
	}

	public EventHandlers<AddEvent> getAddEventHandlers() {
		return addEventHandlers;
	}

	public EventHandlers<DirEvent> getDirEventHandlers() {
		return dirEventHandlers;
	}

	/**
	 * 切换当前路径到根路径
	 * 
	 * @throws IOException
	 */
	public void goRootDir() throws IOException {
		setDir(currentDir.getRoot());
	}

	/**
	 * 切换到当前路径的上层目录
	 */
	public void goParent() {
		if (hasParent()) {
			setDir(currentDir.getParent());
		}
	}

	/**
	 * 获取当前目录
	 * 
	 * @return
	 */
	public FilePath getCurrentDir() {
		return currentDir;
	}

	private void onDir(FilePath oldPath) {
		dirEventHandlers.applyAll(new DirEvent(oldPath, currentDir));
	}

	/**
	 * 全选
	 */
	public void selectAll() {
		items.each(item -> {
			item.select();
		});
	}

	/**
	 * 取消所有已选项（全不选）
	 */
	public void unselectAll() {
		selectedItems.each(item -> {
			item._unselect(); // 注意调用的不是对外公开的select
			});
		selectedItems.clear();
	}

	/**
	 * 在全选和全不选两种状态切换 如果是部分选择，则切换到全选状态
	 */
	public void toggleSelectAll() {
		if (selectedNum() != items.size()) {
			selectAll();
		} else {
			unselectAll();
		}
	}

	/**
	 * 查询已选的文件项
	 */
	public Collection<FileItem> getSelectedItems() {
		return selectedItems.elems();
	}

	/**
	 * 已经选择的个数
	 * 
	 * @return
	 */
	public int selectedNum() {
		return selectedItems.size();
	}

	/**
	 * 反向选择
	 * 
	 * 把已经选住的取消选择，把没有选择的选择上
	 */
	public void reverseSelect() {
		Collection<FileItem> selected = Utils.copy(selectedItems.elems());
		selectedItems.each(item -> {
			item._unselect();
		});
		selectedItems.clear();
		items.each(item -> {
			if (!selected.contains(item)) {
				item.select();
			}
		});
	}

	/**
	 * 添加一个文件过滤器 , 被添加的过滤器会被用来决定是否把文件项加载到文件列表
	 * 
	 * @param filter
	 */
	public void addFilter(FileFilter filter) {
		filters.add(filter);
		flush();
	}

	public void removeFilter(FileFilter filter) {
		filters.remove(filter);
		flush();
	}

	/**
	 * 当不需要这个对象时,调用这个方法以释放占用的资源
	 */
	public void close() {
		if (watcher != null) {
			watcher.close();
			watcher = null;
		}
	}

	/* 千万不要公开这个方法，这个方法仅用于文件项模型向文件列表模型报告自己的选择状态 */
	void addSelected(FileItem item) {
		selectedItems.add(item);
	}

	/* 和addSelected为一组，由FileItemModel内的代码调用，报告选择取消 */
	void removeSelected(FileItem item) {
		selectedItems.remove(item);
	}

	private boolean filt(FilePath file) {
		for (FileFilter filter : filters) {
			if (!filter.filt(file)) {
				return false;
			}
		}
		return true;
	}

	private void setDirWatcher() {
		if (watcher != null) {
			watcher.close();
		}
		try {
			watcher = new DirWatcher(currentDir, () -> {
				Platform.runLater(() -> {
					flush();
				});
			});
			watcher.start();
		} catch (NoSuchFileException ex) {
			watcher = null;
		}
	}
}
